package com.baomidou.config.builder;

import com.alibaba.fastjson.JSON;
import com.baomidou.config.ConstVal;
import com.baomidou.config.DataSourceConfig;
import com.baomidou.config.PackageConfig;
import com.baomidou.config.StrategyConfig;
import com.baomidou.config.TemplateConfig;
import com.baomidou.config.po.TableField;
import com.baomidou.config.po.TableInfo;
import com.baomidou.config.po.ext.BoField;
import com.baomidou.config.po.ext.ParentResult;
import com.baomidou.config.po.ext.Result;
import com.baomidou.config.po.ext.SchemaResult;
import com.baomidou.config.rules.DbType;
import com.baomidou.config.rules.IdStrategy;
import com.baomidou.config.rules.NamingStrategy;
import com.baomidou.config.rules.QuerySQL;
import com.baomidou.config.util.HttpUtil;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugin.logging.SystemStreamLog;

/**
 * 配置汇总 传递给文件生成工具
 *
 * @author YangHu, tangguo, hubin, donggua
 * @since 2016/8/30
 */
public class ConfigBuilder {

    /**
     * SQL连接
     */
    private Connection connection;
    /**
     * SQL语句类型
     */
    private QuerySQL querySQL;
    private String superEntityClass;
    private String superMapperClass;
    /**
     * service超类定义
     */
    private String superServiceClass;
    private String superServiceImplClass;
    private String superControllerClass;
    /**
     * ID的字符串类型
     */
    private String idType;
    /**
     * 数据库表信息
     */
    private List<TableInfo> tableInfoList;

    /**
     * 包配置详情
     */
    private Map<String, String> packageInfo;
    /**
     * 路径配置信息
     */
    private Map<String, String> pathInfo;

    /**
     * 模板路径配置信息
     */
    private TemplateConfig template;

    private StrategyConfig strategyConfig;

    /**
     * 在构造器中处理配置
     *
     * @param outputDir        输出目录
     * @param packageConfig    包配置
     * @param dataSourceConfig 数据源配置
     * @param strategyConfig   表配置
     */
    public ConfigBuilder(PackageConfig packageConfig, DataSourceConfig dataSourceConfig, StrategyConfig strategyConfig,
        TemplateConfig template, String outputDir) {
        handlerPackage(outputDir, packageConfig);
        handlerDataSource(dataSourceConfig);
        handlerStrategy(strategyConfig);
        this.template = template;
        this.strategyConfig = strategyConfig;
    }

    // ************************ 曝露方法 BEGIN*****************************

    public StrategyConfig getStrategyConfig() {
        return strategyConfig;
    }

    /**
     * 所有包配置信息
     *
     * @return 包配置
     */
    public Map<String, String> getPackageInfo() {
        return packageInfo;
    }

    /**
     * 所有路径配置
     *
     * @return 路径配置
     */
    public Map<String, String> getPathInfo() {
        return pathInfo;
    }

    public String getSuperEntityClass() {
        return superEntityClass;
    }

    public String getSuperMapperClass() {
        return superMapperClass;
    }

    /**
     * 获取超类定义
     *
     * @return 完整超类名称
     */
    public String getSuperServiceClass() {
        return superServiceClass;
    }

    public String getSuperServiceImplClass() {
        return superServiceImplClass;
    }

    public String getSuperControllerClass() {
        return superControllerClass;
    }

    /**
     * 获取ID类型
     *
     * @return id生成方式
     */
    public String getIdType() {
        return idType;
    }

    /**
     * 表信息
     *
     * @return 所有表信息
     */
    public List<TableInfo> getTableInfoList() {
        return tableInfoList;
    }

    /**
     * 模板路径配置信息
     *
     * @return 所以模板路径配置信息
     */
    public TemplateConfig getTemplate() {
        return template == null ? new TemplateConfig() : template;
    }

    // ****************************** 曝露方法 END**********************************

    /**
     * 处理包配置
     *
     * @param config PackageConfig
     */
    private void handlerPackage(String outputDir, PackageConfig config) {
        packageInfo = new HashMap<String, String>();
        packageInfo.put(ConstVal.MODULENAME, config.getModuleName());
        packageInfo.put(ConstVal.ENTITY, joinPackage(config.getParent(), config.getEntity()));
        packageInfo.put(ConstVal.MAPPER, joinPackage(config.getParent(), config.getMapper()));
        packageInfo.put(ConstVal.XML, joinPackage("", config.getXml()));
        packageInfo.put(ConstVal.SERIVCE, joinPackage(config.getParent(), config.getService()));
        packageInfo.put(ConstVal.SERVICEIMPL, joinPackage(config.getParent(), config.getServiceImpl()));
        packageInfo.put(ConstVal.CONTROLLER, joinPackage(config.getParent(), config.getController()));

        pathInfo = new HashMap<String, String>();
        pathInfo.put(ConstVal.ENTITY_PATH, joinPath(outputDir, packageInfo.get(ConstVal.ENTITY)));
        pathInfo.put(ConstVal.MAPPER_PATH, joinPath(outputDir, packageInfo.get(ConstVal.MAPPER)));
        pathInfo.put(ConstVal.XML_PATH, packageInfo.get(ConstVal.XML));
        pathInfo.put(ConstVal.SERIVCE_PATH, joinPath(outputDir, packageInfo.get(ConstVal.SERIVCE)));
        pathInfo.put(ConstVal.SERVICEIMPL_PATH, joinPath(outputDir, packageInfo.get(ConstVal.SERVICEIMPL)));
        pathInfo.put(ConstVal.CONTROLLER_PATH, joinPath(outputDir, packageInfo.get(ConstVal.CONTROLLER)));
    }

    /**
     * 处理数据源配置
     *
     * @param config DataSourceConfig
     */
    private void handlerDataSource(DataSourceConfig config) {
        connection = config.getConn();
        querySQL = getQuerySQL(config.getDbType());
    }

    /**
     * 处理数据库表 加载数据库表、列、注释相关数据集
     *
     * @param config StrategyConfig
     */
    private void handlerStrategy(StrategyConfig config) {
        processTypes(config);
        try {
            // 兼容低代码平台奥特曼对象
            if (Boolean.parseBoolean(config.getUltraManOpen())) {
                processUltraMan(config);
            }

            tableInfoList = getTablesInfo(config);
        } finally {
            try {
                if (connection != null) {
                    connection.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void processUltraMan(StrategyConfig config) {
        final StringBuilder schemaSql = new StringBuilder();
        final String loginUrl = config.getUltraManUrl() + "/normal/login";
        final String loginParams = "{\"username\":\"" + config.getUltraManUsername() + "\",\"password\":\"" + config.getUltraManPassword() + "\",\"captcha\":\"\"}";
        final String token = HttpUtil.readToken(loginUrl, loginParams);
        if (token == null) {
            log.info("==========================>loginUrl: " + loginUrl + " loginParams:" + loginParams);
        }
        final String tenantId = StringUtils.defaultIfEmpty(config.getUltraManChangeTenantId(), "4404108364852832145");
        final String changeTenantUrl = config.getUltraManUrl() + "/change-tenant";
        final String changeToken = HttpUtil.changeToken(changeTenantUrl, token, "{\"tenantId\":\"" + tenantId + "\"}");

        final Map<String, String> relations = new HashMap<>();
        for (String item : StringUtils.split(StringUtils.deleteWhitespace(config.getUltraManRelation()), ";")) {
            relations.put(StringUtils.split(item, "=")[0], StringUtils.split(item, "=")[1]);
        }
        relations.forEach((k, v) -> {
            final String serverUrl = config.getUltraManUrl() + String.format(config.getUltraManPath(), v);
            log.info("==========================>serverUrl: " + serverUrl + " changeToken:" + changeToken);
            final SchemaResult schemaResult = HttpUtil.readSchema(changeToken, serverUrl, SchemaResult.class);
            log.info("==========================>schemaResult:" + JSON.toJSONString(schemaResult));
            Optional.ofNullable(schemaResult).ifPresent(result -> {
                if ("0".equals(result.getCode())) {
                    final String objName = underScoreName(k);
                    final String tableName = config.getTablePrefix() + objName;
                    final String dropSql = String.format(" DROP TABLE IF EXISTS `%s`;", tableName);
                    final String createSql = convertCreateSql(config, objName, result.getResult());
                    try {
                        final PreparedStatement dropState = connection.prepareStatement(dropSql);
                        dropState.execute();
                        log.info("==========================>Ultraman Drop Table:" + tableName);
                        final PreparedStatement createState = connection.prepareStatement(createSql);
                        createState.execute();
                        log.info("==========================>Ultraman Create Table:" + tableName);
                        dropState.close();
                        createState.close();
                        schemaSql.append(dropSql).append("\r\n");
                        schemaSql.append(createSql).append("\r\n");
                    } catch (SQLException e) {
                        log.info("执行SQL:" + createSql + " 失败:" + e.getMessage());
                    }
                }
            });
        });
        if (StringUtils.isNotBlank(config.getUltraManSchemaPath())) {
            schemaToFile(config.getUltraManSchemaPath(), schemaSql.toString());
        }
    }

    private void schemaToFile(String schemaSqlPath, String sql) {
        // 生成sql schema
        try {
            if (isCreate(schemaSqlPath)) {
                FileOutputStream fos = new FileOutputStream(schemaSqlPath);
                BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(fos, ConstVal.UTF8));
                writer.write(sql);
                writer.close();
                log.info("==========================>Ultraman schemaToFile success schemaSqlPath:" + schemaSqlPath);
            } else {
                log.info("==========================>Ultraman schemaToFile notFound schemaSqlPath:" + schemaSqlPath);
            }
        } catch (Exception e) {
            log.error("==========================>Ultraman schemaToFile schemaSqlPath: " + schemaSqlPath + " error:" + e.getMessage());
        }
    }

    private boolean isCreate(String filePath) {
        File file = new File(filePath);
        return file.exists();
    }

    private static String underScoreName(String name) {
        StringBuilder result = new StringBuilder();
        if (name != null && name.length() > 0) {
            // 将第一个字符处理成大写
            result.append(name.substring(0, 1).toLowerCase());
            // 循环处理其余字符
            for (int i = 1; i < name.length(); i++) {
                String s = name.substring(i, i + 1);
                // 在大写字母前添加下划线
                if (s.equals(s.toUpperCase()) && !Character.isDigit(s.charAt(0))) {
                    result.append("_");
                }
                // 其他字符直接转成大写
                result.append(s.toLowerCase());
            }
        }
        return result.toString();
    }

    /**
     * DROP TABLE IF EXISTS `t_volume`;
     * CREATE TABLE `t_volume` (
     * `id` bigint(20) NOT NULL COMMENT '主键id',
     * `tenant_id` bigint(20)  COMMENT '租户编号',
     * `tenant_code` varchar(32) COMMENT '租户代码',
     * `create_time` datetime DEFAULT NULL COMMENT '创建时间',
     * `update_time` datetime DEFAULT NULL COMMENT '修改时间',
     * `volume_no` varchar(60) COMMENT '分册号',
     * `volume_count` bigint(20) COMMENT '册数',
     * `borrow_situation` varchar(20) COMMENT '借阅情况',
     * `paper_borrow_situation` varchar(20) COMMENT '纸质借阅情况',
     * PRIMARY KEY (`id`)
     * );
     *
     * @param result
     * @return
     */
    private String convertCreateSql(StrategyConfig config, String objName, Result result) {
        final List<BoField> fields = new ArrayList<>(result.getBoFields());
        final Map parentMap = result.getParentBoMap();
        if (null != parentMap && parentMap.get("0") != null) {
            final ParentResult parentResult = JSON.parseObject(JSON.toJSONString(parentMap.get("0")), ParentResult.class);
            fields.addAll(parentResult.getBoFields());
        }

        final String tableName = config.getTablePrefix() + objName;

        final String brStr = "\r\n";
        StringBuilder sql = new StringBuilder();
        sql.append(String.format(" CREATE TABLE `%s` (", tableName)).append(brStr);
        if (fields.stream().anyMatch(f -> "id".equals(f.getCode()))) {
            sql.append(" `id` bigint(20) NOT NULL COMMENT '主键id', " + brStr);
            fields.removeIf(f -> "id".equals(f.getCode()));
        }
        for (BoField field : fields) {
            sql.append(String.format(" `%s` %s COMMENT '%s', ", field.getCode(), convertType(field), field.getName())).append(brStr);
        }
        sql.append(" PRIMARY KEY (`id`) " + brStr);
        sql.append(" ); ");
        return sql.toString();
    }

    private String convertType(BoField field) {
        // serialNo,string,enum,formula,long,timestamp
        if ("serialNo".equals(field.getType()) || "long".equals(field.getType())) {
            return String.format("bigint(%s)", StringUtils.defaultIfEmpty(field.getMaxLength(), "20"));
        }
        if ("double".equals(field.getType())) {
            return String.format("decimal(%s,%s)", StringUtils.defaultIfEmpty(field.getMaxLength(), "18"),
                StringUtils.defaultIfEmpty(field.getDecimalPoint(), "6"));
        }
        if ("timestamp".equals(field.getType())) {
            return "datetime";
        }
        return String.format("varchar(%s)", StringUtils.defaultIfEmpty(field.getMaxLength(), "32"));
    }

    /**
     * 处理superClassName,IdClassType,IdStrategy配置
     *
     * @param config 策略配置
     */
    private void processTypes(StrategyConfig config) {
        if (StringUtils.isBlank(config.getSuperServiceClass())) {
            superServiceClass = ConstVal.SUPERD_SERVICE_CLASS;
        } else {
            superServiceClass = config.getSuperServiceClass();
        }
        if (StringUtils.isBlank(config.getSuperServiceImplClass())) {
            superServiceImplClass = ConstVal.SUPERD_SERVICEIMPL_CLASS;
        } else {
            superServiceImplClass = config.getSuperServiceImplClass();
        }
        if (StringUtils.isBlank(config.getSuperMapperClass())) {
            superMapperClass = ConstVal.SUPERD_MAPPER_CLASS;
        } else {
            superMapperClass = config.getSuperMapperClass();
        }
        superEntityClass = config.getSuperEntityClass();
        superControllerClass = config.getSuperControllerClass();

        // ID 策略判断
        if (config.getIdGenType() == IdStrategy.auto) {
            idType = IdStrategy.auto.getValue();
        } else if (config.getIdGenType() == IdStrategy.input) {
            idType = IdStrategy.input.getValue();
        } else if (config.getIdGenType() == IdStrategy.none) {
            idType = IdStrategy.none.getValue();
        } else if (config.getIdGenType() == IdStrategy.assign_id) {
            idType = IdStrategy.assign_id.getValue();
        } else if (config.getIdGenType() == IdStrategy.assign_uuid) {
            idType = IdStrategy.assign_uuid.getValue();
        } else {
            idType = IdStrategy.id_worker.getValue();
        }
    }

    /**
     * 处理表对应的类名称
     *
     * @param tableList   表名称
     * @param strategy    命名策略
     * @param tablePrefix
     * @return 补充完整信息后的表
     */
    private List<TableInfo> processTable(List<TableInfo> tableList, NamingStrategy strategy, String tablePrefix) {
        for (TableInfo tableInfo : tableList) {
            tableInfo.setEntityName(NamingStrategy.capitalFirst(processName(tableInfo.getName(), strategy, tablePrefix)));
            tableInfo.setMapperName(tableInfo.getEntityName() + ConstVal.MAPPER);
            tableInfo.setXmlName(tableInfo.getMapperName());
            tableInfo.setServiceName("I" + tableInfo.getEntityName() + ConstVal.SERIVCE);
            tableInfo.setServiceImplName(tableInfo.getEntityName() + ConstVal.SERVICEIMPL);
            tableInfo.setControllerName(tableInfo.getEntityName() + ConstVal.CONTROLLER);
        }
        return tableList;
    }

    private Log log = new SystemStreamLog();

    /**
     * 获取所有的数据库表信息
     *
     * @return 表信息
     */
    private List<TableInfo> getTablesInfo(StrategyConfig config) {
        boolean isInclude = (null != config.getInclude() && config.getInclude().length > 0);
        boolean isExclude = (null != config.getExclude() && config.getExclude().length > 0);
        List<String> includes = Arrays.asList(Optional.ofNullable(config.getInclude()).orElse(new String[0]));
        List<String> excludes = Arrays.asList(Optional.ofNullable(config.getExclude()).orElse(new String[0]));
        List<TableInfo> tableList = new ArrayList<TableInfo>();
        NamingStrategy strategy = config.getNaming();
        NamingStrategy fieldStrategy = config.getFieldNaming();
        String tablePrefix = config.getTablePrefix();
        PreparedStatement pstate = null;
        try {
            pstate = connection.prepareStatement(querySQL.getTableCommentsSql());
            ResultSet results = pstate.executeQuery();
            while (results.next()) {
                String tableName = results.getString(querySQL.getTableName());
                if (tablePrefix != null && !tableName.toUpperCase().startsWith(tablePrefix.toUpperCase())) {
                    continue;
                }
                if (StringUtils.isNotBlank(tableName)) {
                    String includeTableName = tableName.replaceFirst(tablePrefix, "");
                    String tableComment = results.getString(querySQL.getTableComment());
                    TableInfo tableInfo = new TableInfo();
                    if (isInclude) {
                        if (includes.contains(includeTableName)) {
                            tableInfo.setName(tableName);
                            tableInfo.setComment(tableComment);
                        }
                    } else {
                        tableInfo.setName(tableName);
                        tableInfo.setComment(tableComment);
                    }
                    if (isExclude) {
                        if (excludes.contains(includeTableName)) {
                            continue;
                        }
                    }
                    if (StringUtils.isNotBlank(tableInfo.getName())) {
                        List<TableField> fieldList = getListFields(tableInfo.getName(), fieldStrategy);
                        tableInfo.setFields(fieldList);
                        tableList.add(tableInfo);
                    }
                } else {
                    System.err.println("当前数据库为空！！！");
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            try {
                if (pstate != null) {
                    pstate.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return processTable(tableList, strategy, config.getTablePrefix());
    }

    /**
     * 将字段信息与表信息关联
     *
     * @param tableName 表名称
     * @param strategy  命名策略
     * @return 表信息
     */
    private List<TableField> getListFields(String tableName, NamingStrategy strategy) throws SQLException {
        boolean havedId = false;

        PreparedStatement pstate = connection.prepareStatement(String.format(querySQL.getTableFieldsSql(), tableName));
        ResultSet results = pstate.executeQuery();

        List<TableField> fieldList = new ArrayList<TableField>();
        while (results.next()) {
            TableField field = new TableField();
            String key = results.getString(querySQL.getFieldKey());
            // 避免多重主键设置，目前只取第一个找到ID，并放到list中的索引为0的位置
            boolean isId = StringUtils.isNotBlank(key) && key.toUpperCase().equals("PRI");
            // 处理ID
            if (isId && !havedId) {
                field.setKeyFlag(true);
                havedId = true;
            } else {
                field.setKeyFlag(false);
            }
            // 处理其它信息
            field.setName(results.getString(querySQL.getFieldName()));
            field.setType(results.getString(querySQL.getFieldType()));
            field.setPropertyName(processName(field.getName(), strategy));
            field.setPropertyType(processFiledType(field.getType()));
            field.setComment(results.getString(querySQL.getFieldComment()));
            fieldList.add(field);
        }
        return fieldList;
    }

    /**
     * 连接路径字符串
     *
     * @param parentDir   路径常量字符串
     * @param packageName 包名
     * @return 连接后的路径
     */
    private String joinPath(String parentDir, String packageName) {
        if (StringUtils.isEmpty(parentDir)) {
            parentDir = System.getProperty(ConstVal.JAVA_TMPDIR);
        }
        if (!StringUtils.endsWith(parentDir, File.separator)) {
            parentDir += File.separator;
        }
        packageName = packageName.replaceAll("\\.", "\\" + File.separator);
        return parentDir + packageName;
    }

    /**
     * 连接父子包名
     *
     * @param parent     父包名
     * @param subPackage 子包名
     * @return 连接后的包名
     */
    private String joinPackage(String parent, String subPackage) {
        if (StringUtils.isBlank(parent)) {
            return subPackage;
        }
        return parent + "." + subPackage;
    }

    /**
     * 处理字段类型
     *
     * @return 转换成JAVA包装类型
     */
    private String processFiledType(String type) {
        if (QuerySQL.MYSQL == querySQL) {
            return processMySqlType(type);
        } else if (QuerySQL.ORACLE == querySQL) {
            return processOracleType(type);
        }
        return null;
    }

    /**
     * 处理字段名称
     *
     * @return 根据策略返回处理后的名称
     */
    private String processName(String name, NamingStrategy strategy) {
        return processName(name, strategy, null);
    }

    /**
     * 处理字段名称
     *
     * @param name
     * @param strategy
     * @param tablePrefix
     * @return 根据策略返回处理后的名称
     */
    private String processName(String name, NamingStrategy strategy, String tablePrefix) {
        String propertyName = "";
        switch (strategy) {
            case remove_prefix_and_camel:
                propertyName = NamingStrategy.removePrefixAndCamel(name, tablePrefix);
                break;
            case underline_to_camel:
                propertyName = NamingStrategy.underlineToCamel(name);
                break;
            case remove_prefix:
                propertyName = NamingStrategy.removePrefix(name, tablePrefix);
                break;
            case remove_underline:
                propertyName = NamingStrategy.removeUnderline(name);
                break;
            case remove_underline_and_camel:
                propertyName = NamingStrategy.removeUnderlineAndCamel(name);
                break;
            default:
                propertyName = name;
        }
        return propertyName;
    }

    /**
     * MYSQL字段类型转换
     *
     * @param type 字段类型
     * @return JAVA类型
     */
    private String processMySqlType(String type) {
        String t = type.toLowerCase();
        if (t.contains("char") || t.contains("text")) {
            return "String";
        } else if (t.contains("bigint")) {
            return "Long";
        } else if (t.contains("int")) {
            return "Integer";
        } else if (t.contains("date") || t.contains("time") || t.contains("year")) {
            return "Date";
        } else if (t.contains("text")) {
            return "String";
        } else if (t.contains("bit")) {
            return "Boolean";
        } else if (t.contains("decimal")) {
            return "BigDecimal";
        } else if (t.contains("blob")) {
            return "byte[]";
        } else if (t.contains("float")) {
            return "Float";
        } else if (t.contains("double")) {
            return "Double";
        } else if (t.contains("json") || t.contains("enum")) {
            return "String";
        }
        return "String";
    }

    /**
     * ORACLE字段类型转换
     *
     * @param type 字段类型
     * @return JAVA类型
     */
    private String processOracleType(String type) {
        String t = type.toUpperCase();
        if (t.contains("CHAR")) {
            return "String";
        } else if (t.contains("DATE") || t.contains("TIMESTAMP")) {
            return "Date";
        } else if (t.contains("NUMBER")) {
            if (t.matches("NUMBER\\(+\\d{1}+\\)")) {
                return "Integer";
            } else if (t.matches("NUMBER\\(+\\d{2}+\\)")) {
                return "Long";
            }
            return "Double";
        } else if (t.contains("FLOAT")) {
            return "Float";
        } else if (t.contains("BLOB")) {
            return "Object";
        } else if (t.contains("RAW")) {
            return "byte[]";
        }
        return "String";
    }

    /**
     * 获取当前的SQL类型
     *
     * @return DB类型
     */
    private QuerySQL getQuerySQL(DbType dbType) {
        for (QuerySQL qs : QuerySQL.values()) {
            if (qs.getDbType().equals(dbType.getValue())) {
                return qs;
            }
        }
        return QuerySQL.MYSQL;
    }

}
